/*    This file is part of libepistle.
 *
 *    libepistle is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    libepistle is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with libepistle.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>

#include "polling.h"

struct pm_extra {
	Pm_react react;
	Server_check s_check;
	void *extra;
};

int
poll_add(int epoll_fd, int fd, Err *err)
{
	struct epoll_event event = { 0 };

	event.events = EPOLLIN;
	event.data.fd = fd;

	if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) {
		err_std(err);
		return -1;
	}

	return 0;
}

int
poll_remove(int epoll_fd, int fd, Err *err)
{
	if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) {
		err_std(err);
		return -1;
	}

	return 0;
}

int
poll_out(int epoll_fd, int fd, Err *err)
{
	struct epoll_event event = { 0 };

	event.events = EPOLLIN | EPOLLOUT;
	event.data.fd = fd;

	if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) {
		err_std(err);
		return -1;
	}

	return 0;
}

int
poll_out_off(int epoll_fd, int fd, Err *err)
{
	struct epoll_event event = { 0 };

	event.events = EPOLLIN;
	event.data.fd = fd;

	if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) {
		err_std(err);
		return -1;
	}

	return 0;
}

int
polling(int *epoll_fd, Polling_react react, int fds[], Err *err, void *extra)
{
	struct epoll_event events[16];

	if (*epoll_fd < 0 && (*epoll_fd = epoll_create1(0)) < 0) {
		err_std(err);
		return -1;
	}

	if (fds)
		for (int i = 0; fds[i] >= 0; ++i)
			if (poll_add(*epoll_fd, fds[i], err) < 0)
			        return -1;

	while (1) {
		int react_val = POLLING_OKAY;
		int nfds = epoll_wait(*epoll_fd, events,
				      sizeof(events) / sizeof(events[0]), -1);

		if (nfds < 0) {
			/* laptop closing can cause this! */
			if (errno == EINTR)
				continue;

		        err_std(err);
			return -1;
		}

		for (int i = 0; i < nfds && react_val == POLLING_OKAY; ++i)
			react_val = react(*epoll_fd, events[i].data.fd,
					  events[i].events, err, extra);

		if (react_val == POLLING_OKAY)
			continue;

		if (react_val > 0) /* POLLING END */
			break;

		/* POLLING FATAL */
		return -1;
	}

	return 0;
}

static int
pm_react(int epoll_fd, int fd, uint32_t events, Err *err, void *extra)
{
	int pm_event = 0;
	struct pm_extra *pm_extra = extra;
	int server_fd = -1;
	char check;

	if (pm_extra->s_check &&
	    pm_extra->s_check(fd, pm_extra->extra)) {
		int client = accept(fd, NULL, NULL);

		/* We don't do anything, it's the clients problem */
		if (client < 0)
			return POLLING_OKAY;

		server_fd = fd;
		fd = client;
		pm_event = PM_ADD;
	} else if (events & EPOLLIN) {
		pm_event = recv(fd, &check, 1, MSG_PEEK | MSG_DONTWAIT) > 0 ?
			PM_INPUT : PM_REMOVE;
	} else if (events & EPOLLOUT) {
		pm_event = PM_OUTPUT;
	}

	if (!pm_event)
		return POLLING_OKAY;

	return pm_extra->react(epoll_fd, fd, server_fd, pm_event,
			      err, pm_extra->extra);
}

int
polling_mode(Pm_react react, int *epoll_fd, int fds[], Server_check s_check,
	     Err *err, void *extra)
{
	struct pm_extra pm_extra = {
		.react = react,
		.s_check = s_check,
		.extra = extra
	};

	return polling(epoll_fd, pm_react, fds, err, &pm_extra);
}
